package com.ihr360.rest.webmvc;

import com.google.common.collect.Lists;
import com.ihr360.authority.sharing.annonation.RestControl;
import com.ihr360.authority.sharing.model.IResourceControl;
import com.ihr360.authority.sharing.privilege.AccessControlMethod;
import com.ihr360.authority.sharing.privilege.IAccessControlClass;
import com.ihr360.authority.sharing.privilege.SearchPrivilege;
import com.ihr360.authority.sharing.service.AuthUserResourceService;
import com.ihr360.authority.sharing.util.AccessControlUtils;
import com.ihr360.commons.context.session.Ihr360Session;
import com.ihr360.commons.context.session.Ihr360SessionContextHolder;
import com.ihr360.commons.entity.AuthUserInfo;
import com.ihr360.commons.exception.Ihr360Exception;
import com.ihr360.commons.exception.Ihr360SimpleResultException;
import com.ihr360.commons.lang.CollectionUtils;
import com.ihr360.commons.lang.Ihr360ClassUtils;
import com.ihr360.commons.lang.PageUtil;
import com.ihr360.commons.lang.StringUtils;
import com.ihr360.commons.model.type.ICompanyId;
import com.ihr360.commons.request.Ihr360SearchBody;
import com.ihr360.commons.specification.Ihr360Predication;
import com.ihr360.commons.specification.Ihr360Specification;
import com.ihr360.commons.specification.PredicationOperator;
import com.ihr360.rest.service.SimpleRestService;
import com.ihr360.validation.ValidationUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import static com.ihr360.authority.sharing.privilege.AccessControlMethod.METHODS.*;
import javax.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;

/**
 * @param <T>
 * @author david.wei
 */
@RestController
public abstract class AbstractRestSimpleWithLongIdController<T> {

    private static final Logger logger = LoggerFactory.getLogger(AbstractRestSimpleWithLongIdController.class);

    private static final String COMPANY_ID = "companyId";
    private static final String COMPANY_ID_SYS = "SYSTEM";

    @Autowired
    private ResourceUtils resourceUtils;


    @Autowired
    AuthUserResourceService authUserResourceService;

    public AuthUserResourceService getAuthUserResourceService() {
        return authUserResourceService;
    }

    public IAccessControlClass getiAccessControlClass() {
        return iAccessControlClass;
    }

    /**
     * 获取SimpleRestService实现
     *
     * @return
     */
    public abstract SimpleRestService<T, Long> getSimpleRestService();

    private Class idClass;
    private Class entityClass;
    private Class serviceClass;
    List accessDenyList;
    private IAccessControlClass iAccessControlClass = new IAccessControlClass() {
        @Override
        public Class getServiceClass() {
            return serviceClass;
        }

        @Override
        public Class getEntityClass() {
            return entityClass;
        }

        @Override
        public Class getIdClass() {
            return idClass;
        }
    };

    @PostConstruct
    public void initPostConstruct() {
        entityClass = Ihr360ClassUtils.getGenericSuperClassActualTypeArgument(this.getClass(), 0);
        idClass = Long.class;
        serviceClass = getSimpleRestService().getClass();
        RestControl annotation = AnnotationUtils.findAnnotation(this.getClass(), RestControl.class);
        if (annotation == null) {
            // 默认控制
            accessDenyList = Collections.emptyList();
            return;
        }
        switch (annotation.mode()) {
            case DENY:
                if (annotation.controlAll()) {
                    accessDenyList = AccessControlMethod.ALL_METHODS;
                } else {
                    accessDenyList = Arrays.asList(annotation.controls());
                }
                break;
            case ALLOW:
                if (annotation.controlAll()) {
                    accessDenyList = Collections.emptyList();
                } else {
                    List<String> allList = Lists.newArrayList(AccessControlMethod.ALL_METHODS);
                    allList.removeAll(Arrays.asList(annotation.controls()));
                    accessDenyList = allList;
                }
                break;
            default:
                ValidationUtils.reject(null, "接口访问控制错误");
                break;
        }

    }

    @DeleteMapping(value = "/", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
    public Boolean delete(@RequestBody Long[] ids) {
        ValidationUtils.rejectIfTrue(accessDenyList.contains(AccessControlMethod.METHODS.batchDelete), null, "访问资源拒绝服务");
        try {
            getSimpleRestService().batchDelete(Arrays.asList(ids));
        } catch (Ihr360Exception e) {
            throw e;
        } catch (DataIntegrityViolationException e) {
            logger.warn(e.getMessage(), e);
            throw new Ihr360Exception(null,
                    ids.length > 1
                            ? "部分要删除的数据已被其他数据关联，请尝试逐一删除"
                            : "要删除的数据已被其他数据关联，无法删除");
        } catch (Exception e) {
            logger.warn(e.getMessage(), e);
            throw new Ihr360Exception(null, "无法删除数据，请刷新后重试");
        }
        return true;
    }

    @DeleteMapping(value = "/{id}", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
    public Boolean delete(@PathVariable(name = "id") Long id) {
        ValidationUtils.rejectIfTrue(accessDenyList.contains(AccessControlMethod.METHODS.deleteOne), null, "访问资源拒绝服务");
        try {
            getSimpleRestService().deleteOne(id);
        } catch (Ihr360Exception e) {
            throw e;
        } catch (DataIntegrityViolationException e) {
            logger.warn(e.getMessage(), e);
            throw new Ihr360Exception(null, "要删除的数据已被其他数据关联，无法删除");
        } catch (Exception e) {
            logger.warn(e.getMessage(), e);
            throw new Ihr360Exception(null, "无法删除数据，请刷新后重试");
        }
        return true;
    }

    @GetMapping(value = "/{id}", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
    public ResponseEntity<?> get(@PathVariable(name = "id") Long id, @RequestParam(name = "projection", required = false) String projection) {
        ValidationUtils.rejectIfTrue(accessDenyList.contains(AccessControlMethod.METHODS.getOne), null, "访问资源拒绝服务");
        T t = getSimpleRestService().getOne(id);
        return resourceUtils.toResource(t, projection);
    }

    @GetMapping(value = "/", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
    public ResponseEntity<?> getAll(@RequestParam(name = "projection", required = false) String projection) {
        ValidationUtils.rejectIfTrue(accessDenyList.contains(AccessControlMethod.METHODS.getAll), null, "访问资源拒绝服务");
        Iterable<T> t = getSimpleRestService().getAll();
        return resourceUtils.toResource(t, projection);
    }

    @PatchMapping(value = "/{id}", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
    public ResponseEntity<?> patch(@PathVariable(name = "id") Long id, @RequestParam(name = "projection", required = false) String projection, @RequestBody Map<String, Object> t) {
        ValidationUtils.rejectIfTrue(accessDenyList.contains(AccessControlMethod.METHODS.update), null, "访问资源拒绝服务");
        T ret;
        try {
            ret = getSimpleRestService().update(id, t);
        } catch (DataIntegrityViolationException e) {
            logger.warn(e.getMessage(), e);
            throw generateUniqueConstraintIhr360Exception();
        } catch (Ihr360Exception e) {
            throw e;
        } catch (Exception e) {
            logger.warn(e.getMessage(), e);
            throw new Ihr360Exception(null, "无法保存数据，请刷新后重试");
        }
        return resourceUtils.toResource(ret, projection);
    }

    @PutMapping(value = "/", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
    public ResponseEntity<?> create(@RequestParam(name = "projection", required = false) String projection,
                                    @RequestParam(name = "noBeanReturn", required = false) boolean noBeanReturn,
                                    @RequestBody T map) {
        ValidationUtils.rejectIfTrue(accessDenyList.contains(AccessControlMethod.METHODS.create), null, "访问资源拒绝服务");
        T ret;
        try {
            ret = getSimpleRestService().create(map);
        } catch (Ihr360Exception e) {
            throw e;
        } catch (DataIntegrityViolationException e) {
            logger.warn(e.getMessage(), e);
            throw generateUniqueConstraintIhr360Exception();
        } catch (Exception e) {
            logger.warn(e.getMessage(), e);
            throw new Ihr360Exception(null, "无法创建数据，请刷新后重试");
        }
        if (noBeanReturn) {
            throw Ihr360SimpleResultException.builder().data(null).build();
        }
        return resourceUtils.toResource(ret, projection);
    }

    @PostMapping(value = "/search", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
    public ResponseEntity<?> executeSearch(@RequestBody Ihr360SearchBody<T> searchBody, @RequestParam(name = "projection", required = false) String projection) {
        ValidationUtils.rejectIfTrue(accessDenyList.contains(AccessControlMethod.METHODS.search), null, "访问资源拒绝服务");
        setCompanyId(searchBody);
        SearchPrivilege searchPrivilege = new SearchPrivilege<>(authUserResourceService, iAccessControlClass, searchBody).doValidate();
        Page<T> page = getSimpleRestService().search(searchBody.getSpecification(), searchBody.getPageable(), searchPrivilege.isNoData());
        if (searchPrivilege.isNoData()) {
            return resourceUtils.toResource(page, projection);
        }
        List<T> content = page.getContent();
        Map<Long, Integer> privilegeMap = searchPrivilege.getUserResourcesPrivilege();
        Map<Long, Integer> publicityMap = searchPrivilege.getUserResourcesPublicity();
        if (IResourceControl.class.isAssignableFrom(entityClass)) {
            content.forEach(s ->
                            AccessControlUtils.setAccessControl2Target(s, privilegeMap, publicityMap)
            );
        }
        return resourceUtils.toResource(page, projection);
    }


    protected Pageable getPagable(String[] sortProp, Boolean sortAsc, String defaultSortProp, Integer page, Integer rows) {
        Sort sort;
        if (CollectionUtils.isNotEmpty(sortProp) && sortAsc != null) {
            if (sortAsc) {
                sort = new Sort(Sort.Direction.ASC, sortProp);
            } else {
                sort = new Sort(Sort.Direction.DESC, sortProp);
            }
        } else {
            sort = new Sort(Sort.Direction.DESC, defaultSortProp);
        }
        return PageUtil.getPageable(page, rows, sort);
    }

    public void setCompanyId(Ihr360SearchBody<T> searchBody) {
        Class trueClass;
        try {
            trueClass = Ihr360ClassUtils.getGenericSuperClassActualTypeArgument(this.getClass(), 0);
        } catch (Exception e) {
            logger.warn(e.getMessage(), e);
            return;
        }

        if (!ICompanyId.class.isAssignableFrom(trueClass)) {
            return;
        }

        if (searchBody.getSpecification() == null) {
            searchBody.setSpecification(new Ihr360Specification<>());
        }

        Ihr360Specification specification = (Ihr360Specification) searchBody.getSpecification();
        List<Ihr360Predication> predications = specification.getPredications();
        if (predications == null) {
            predications = new ArrayList<>();
            specification.setPredications(predications);
        }

        boolean isSystemCompanyIdContains = false;
        for (Ihr360Predication predication : predications) {
            if (COMPANY_ID.equals(predication.getFieldName())
                    && COMPANY_ID_SYS.equals(predication.getFieldValue())) {
                isSystemCompanyIdContains = true;
            }
        }
        if (!isSystemCompanyIdContains) {
            String companyId = getCompanyId();
            if (StringUtils.isNotBlank(companyId)) {
                Ihr360Predication companyIdPredication = new Ihr360Predication();
                companyIdPredication.setFieldName(COMPANY_ID);
                companyIdPredication.setOperator(PredicationOperator.EQUALS);
                companyIdPredication.setFieldValue(companyId);
                predications.add(0, companyIdPredication);
            }
        }
    }

    private String getCompanyId() {
        Ihr360Session session = Ihr360SessionContextHolder.getSession();
        if (session != null) {
            AuthUserInfo userInfo = session.getUserInfo();
            if (userInfo != null) {
                String companyId = userInfo.getCompanyId();
                if (StringUtils.isNotBlank(companyId)) {
                    return companyId;
                }
            }
        }
        return null;
    }

    /**
     * 生成Ihr360唯一约束异常类
     * Notes: 如果需要个性message，在自己的Controller中进行重写
     *
     * @return Ihr360Exception
     */
    protected Ihr360Exception generateUniqueConstraintIhr360Exception() {
        return new Ihr360Exception(null, "已经存在相同的数据");
    }

    protected ResponseEntity<?> toResource(final Object source, String projection) {
        return resourceUtils.toResource(source,projection);
    }
}
